#include <os/driver.h>
#include <os/initcall.h>
#include <os/memcache.h>
#include <os/debug.h>
#include <arch/x86.h>
#include <arch/page.h>
#include <arch/cursor.h>
#include <arch/text.h>
#include <lib/stdio.h>
#include <lib/type.h>
#include <lib/string.h>
#include <sys/res.h>
#include <sys/ioctl.h>

#define DRIVER_NAME "console-driver"
#define DRIVER_VER "0.1"
#define DEVICE_NAME "con"

#define CONSOLE_NUM 8 // number of consoles

#define CONSOLE_WIDTH 80
#define CONSOLE_HEIGHT 25

#define CONSOLE_SIZE (CONSOLE_WIDTH * CONSOLE_HEIGHT)

#define CONSOLE_VBUFF (PAGE_HIGH_BASE + 0xb8000)

#define CONSOLE_COLOR_MASK(back, front) ((back << 4) | front)

#define IS_PRINTCHAR(ch) ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ((ch >= 32) && (ch <= 126)))

typedef struct __device_extension
{
    uint32_t id;
    uint32_t width;
    uint32_t height;
    uint32_t size;
    uint32_t x;
    uint32_t y;
    uint8_t *vbuff;
    uint8_t backcolor;
    uint8_t frontcolor;
} device_extension_t;

static void ConsoleScrollScreen(device_extension_t *extension)
{
    uint8_t *src;
    uint8_t *targe;
    int i, j, k;
    uint8_t *buff = extension->vbuff;

    for (i = 1; i < extension->height; i++)
    {
        j = i - 1;
        src = buff + i * extension->width * 2;
        targe = buff + j * extension->width * 2;
        for (k = 0; k < extension->width; k++)
        {
            *(targe + k * 2) = *(src + k * 2);
            *(targe + k * 2 + 1) = *(src + k * 2 + 1);
        }
    }

    // clear empty line context
    for (k = 0; k < 80; k++)
    {
        *(src + k * 2) = 0x20;
        *(src + k * 2 + 1) = CONSOLE_COLOR_MASK(TEXT_BACKGROUNTCOLOR_BLACK, TEXT_FRONTCOLOR_WHITE);
    }
}

static void ConsoleClean(device_extension_t *extension)
{
    int off;
    uint8_t *p = extension->vbuff;
    uint8_t color = CONSOLE_COLOR_MASK(extension->backcolor, extension->frontcolor);

    for (off = 0; off < CONSOLE_SIZE; off++)
    {
        *(uint8_t *)(p + off * 2 + 0) = 0x20;
        *(uint8_t *)(p + off * 2 + 1) = color;
    }

    // reset position
    extension->x = 0;
    extension->y = 0;
    // reset cursor to start
    SetCursor(0, 0);
}

static void ConsolePutC(device_extension_t *extension, char ch)
{
    int position;

    if (extension->y < extension->height)
    {
        if (extension->x < extension->width)
        {
            if (ch != '\n' && ch != '\b')
            {
                if (!IS_PRINTCHAR(ch)) // no print char
                    return;

                TextPutChar(ch, CONSOLE_COLOR_MASK(extension->backcolor, extension->frontcolor));
                // next char
                extension->x++;

                // update cursor
                position = ReadCursor();
                WriteCursor(position + 1);

                if (extension->x >= extension->width)
                {
                    extension->y++;
                    extension->x = 0;
                }
            }
            else if (ch == '\n' || ch == '\0')
            {
                extension->y++;
                extension->x = 0;

                // next line
                position = ReadCursor();
                WriteCursor(position + (CHARS_PER_LINE - (position % CHARS_PER_LINE)));
            }
            else
            {
                extension->x--;
                if (extension->x < 0)
                {
                    extension->y--;
                    extension->x = extension->width - 1;
                }

                position = ReadCursor();
                WriteCursor(position - 1);

                // fill empty char(Ascii code: 32)
                TextPutChar(32, CONSOLE_COLOR_MASK(extension->backcolor, extension->frontcolor));
            }
        }
        else
        {
            // above line
            extension->y++;
            extension->x = 0;
            // next line
            position = ReadCursor();
            WriteCursor(position + (CHARS_PER_LINE - (position % CHARS_PER_LINE)));
        }
    }

    if (extension->y >= extension->height)
    {
        ConsoleScrollScreen(extension);
        // go to last line
        extension->y--;
        extension->x = 0;
        SetCursor(0, extension->y);
    }
}

static iostatus_t ConsoleEnter(driver_object_t *driver)
{
    iostatus_t status = IO_FAILED;
    device_object_t *device = NULL;
    char devname[DEVICE_NAME_LEN+1];
    device_extension_t *extension;

    for (int i = 0; i < CONSOLE_NUM; i++)
    {
        memset(devname, 0, 32);
        sprintf(devname, "%s%d", DEVICE_NAME, i);
        status = IoCreateDevice(driver, sizeof(device_extension_t), devname, DEVICE_TYPE_VIRTUAL_CHAR, &device);
        if (status != IO_SUCCESS)
        {
            KPrint("[console] create device failed!\n");
            return IO_FAILED;
        }
        // neither io mode
        device->flags = 0;
        extension = device->device_extension;
        extension->id = 0;

        // set attr
        extension->width = CONSOLE_WIDTH;
        extension->height = CONSOLE_HEIGHT;
        extension->size = CONSOLE_SIZE;
        extension->x = 0;
        extension->y = 0;
        extension->vbuff = CONSOLE_VBUFF;
        extension->backcolor = TEXT_BACKGROUNTCOLOR_BLACK;
        extension->frontcolor = TEXT_FRONTCOLOR_WHITE;
    }

    return IO_SUCCESS;
}

static iostatus_t ConsoleExit(driver_object_t *driver)
{
    device_object_t *device, *next;
    list_traversal_all_owner_to_next_safe(device, next, &driver->device_list, list)
    {
        list_del_init(&device->list);
    }
    string_del(&driver->name);

    return IO_SUCCESS;
}

static iostatus_t ConsoleOpen(device_object_t *device, io_request_t *ioreq)
{
    iostatus_t status = IO_SUCCESS;
    ioreq->io_status.status = status;
    ioreq->io_status.info = 0;

    // clean screen
    ConsoleClean(device->device_extension);
    IoCompleteRequest(ioreq);
    return status;
}

static iostatus_t ConsoleClose(device_object_t *device, io_request_t *ioreq)
{
    iostatus_t status = IO_SUCCESS;
    ioreq->io_status.status = status;
    ioreq->io_status.info = 0;
    IoCompleteRequest(ioreq);
    return status;
}

static iostatus_t ConsoleWrite(device_object_t *device, io_request_t *ioreq)
{
    device_extension_t *extension = device->device_extension;
    iostatus_t status = IO_SUCCESS;
    uint8_t *p = ioreq->user_buff;

    ConsolePutC(extension, *p);

    ioreq->io_status.status = status;
    ioreq->io_status.info = 0;
    IoCompleteRequest(ioreq);
    return status;
}

static iostatus_t ConsoleDevCtl(device_object_t *device, io_request_t *ioreq)
{
    device_extension_t *extension = device->device_extension;
    uint32_t cmd = ioreq->parame.devctl.code;
    uint32_t arg = ioreq->parame.devctl.arg;

    switch (cmd)
    {
    case CONIO_CLEAN:
        KPrint("[console] clean\n");
        ConsoleClean(extension);
        break;
    case CONIO_ID:
        *(uint32_t *)arg = extension->id;
        break;
    case CONIO_SETCOLOR_BACK:
        extension->backcolor = *(uint32_t *)arg;
        break;
    case CONIO_SETCOLOR_FRONT:
        extension->frontcolor = *(uint32_t *)arg;
        break;
    case CONIO_GETCOLOR_BACK:
        *(uint32_t *)arg = extension->backcolor;
        break;
    case CONIO_GETCOLOR_FRONT:
        *(uint32_t *)arg = extension->frontcolor;
        break;
    case CONIO_GETSIZE:
        *(uint32_t *)arg = extension->size;
        break;
    case CONIO_GETWIDTH:
        *(uint32_t *)arg = extension->width;
        break;
    case CONIO_GETHEIGHT:
        *(uint32_t *)arg = extension->height;
        break;
    default:
        break;
    }
    ioreq->io_status.status = IO_SUCCESS;
    ioreq->io_status.info = 0;
    IoCompleteRequest(ioreq);
    return IO_SUCCESS;
}

static iostatus_t ConsoleDriverFunc(driver_object_t *driver)
{
    string_new(&driver->name, DRIVER_NAME, DEVICE_NAME_LEN);

    driver->driver_enter = ConsoleEnter;
    driver->driver_exit = ConsoleExit;
    driver->dispatch_fun[IOREQ_OPEN] = ConsoleOpen;
    driver->dispatch_fun[IOREQ_CLOSE] = ConsoleClose;
    driver->dispatch_fun[IOREQ_WRITE] = ConsoleWrite;
    driver->dispatch_fun[IOREQ_DEVCTL] = ConsoleDevCtl;

    return IO_SUCCESS;
}

static __init void ConsoleDriverEntry()
{
    if (DriverObjectCreate(ConsoleDriverFunc) < 0)
        KPrint("[driver] console driver create failed!\n");
    KPrint("[driver] create console driver ok!\n");
}

driver_initcall(ConsoleDriverEntry);